package com.duolebo.uteped_sdk.utils;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.text.TextUtils;

import com.duolebo.uteped_sdk.UTEPedMethodCallHandler;
import com.duolebo.uteped_sdk.db.IRecord;
import com.duolebo.uteped_sdk.db.Table;
import com.duolebo.uteped_sdk.db.UTEPedDb;
import com.duolebo.uteped_sdk.model.SleepModel;
import com.duolebo.uteped_sdk.model.StressModel;
import com.duolebo.uteped_sdk.model.WorkoutModel;
import com.google.gson.Gson;
import com.yc.nadalsdk.bean.FitnessData;
import com.yc.nadalsdk.bean.Response;
import com.yc.nadalsdk.bean.SleepData;
import com.yc.nadalsdk.bean.StressData;
import com.yc.nadalsdk.bean.WorkoutData;
import com.yc.nadalsdk.ble.open.UteBleClient;
import com.yc.nadalsdk.ble.open.UteBleConnection;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @Author: LiuYang
 * @Date: 2023/12/27 18:13
 * @Description:
 */
public class DataSyncHelper {
    private final static String TAG = "DataSyncHelper";
    private static DataSyncHelper instance;

    public static DataSyncHelper getInstance() {
        if (instance == null) {
            instance = new DataSyncHelper();
        }

        return instance;
    }

    private UTEPedDb utePedDb;
    private boolean isSyncRunning = false;
    private Timer syncDataTimer;
    private final Handler handler = new Handler();
    private boolean changeDBFlag = false;

    public DataSyncHelper() {
        utePedDb = UTEPedDb.shareInstance();
    }

    public void changeDB() {
        if (!isSyncRunning) {
            utePedDb = UTEPedDb.getInstance(Tools.getApplicationContext());
            changeDBFlag = false;
        }
        else {
            changeDBFlag = true;
        }
    }

    private List<? extends IRecord> getNewestOne(String tableName, String orderBy) {
        return utePedDb.getTable(tableName).query(orderBy, "1");
    }

    private int getTimeInSecondsByDay(int days) {
        Calendar cal = Calendar.getInstance();
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE), 0, 0, 0);
        cal.add(Calendar.DATE, days);
        return (int) (cal.getTimeInMillis() / 1000);
    }

    private int getTimeInSecondsByMinute(int minutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(new Date());
        cal.add(Calendar.MINUTE, minutes);
        return (int) (cal.getTimeInMillis() / 1000);
    }

    private int getTimeInSecondsByDate(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.clear();
        cal.setTime(date);
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE), 0, 0, 0);
        return (int) (cal.getTimeInMillis() / 1000);
    }

    private void fetchData(String action) {
        Logger.d(TAG, "syncData...");
        isSyncRunning = true;
        try {
            UteBleClient uteBleClient = UteBleClient.getUteBleClient();
            if (!uteBleClient.isBluetoothEnable() || !uteBleClient.isConnected()) {
                Logger.d(TAG, "syncData device not connected.");
                isSyncRunning = false;
                syncDataComplete(Const.STATE_DEVICE_DISCONNECTED);
                return;
            }

            UteBleConnection connection = uteBleClient.getUteBleConnection();
            String address = uteBleClient.getDeviceAddress();
            if (TextUtils.isEmpty(address)) {
                Logger.d(TAG, "syncData address is empty");
                isSyncRunning = false;
                syncDataComplete(Const.STATE_DEVICE_DISCONNECTED);
                return;
            }

            Logger.d(TAG, "syncData...start");

            // 如果手表不支持数据主动更新通知，考虑使用该方案
            boolean fetchLatest = (Const.ACTION_REMOTE_REFRESH.equals(action));
            fetchLatest = false;

            fetchSleepData(connection, address);
            fetchStressData(connection, address, fetchLatest);
            fetchWorkoutData(connection, address, fetchLatest);
            fetchFitnessData(connection, address, fetchLatest);

            handler.post(() -> syncDataComplete(""));

        } catch (Exception e) {
            e.printStackTrace();
        }

        Logger.d(TAG, "syncData...end");
        isSyncRunning = false;
        cancelSyncDataTimer();
        if (changeDBFlag) {
            changeDB();
        }
    }

    private void fetchFitnessData(UteBleConnection connection, String address, boolean latest) {
        int startTime = latest ? getTimeInSecondsByMinute(-5) : getTimeInSecondsByDay(-2);
        int endTime = latest ? getTimeInSecondsByMinute(0) : getTimeInSecondsByDay(2);

        Response<FitnessData> fitnessData = connection.syncFitnessHistoryData(startTime, endTime);
        if (fitnessData.getErrorCode() == Const.CODE_SUCCESS) {
            // for debug
//            if (SPHelper.shareInstance().getBoolean(Const.M_setLogFileEnable, false)) {
//                String content = new Gson().toJson(fitnessData);
//                Tools.saveFile(content, "/sdcard/Download/" + "fitnessData_" + System.currentTimeMillis() + ".json");
//            }

            utePedDb.saveFitnessData(fitnessData.getData(), startTime, endTime, address);
        } else {
            Logger.d(TAG, "fitnessData data error code: " + fitnessData.getErrorCode());
        }
    }

    private void fetchWorkoutData(UteBleConnection connection, String address, boolean latest) {
        int startTime;
        int endTime;
        if (latest) {
            startTime = getTimeInSecondsByMinute(-5);
            endTime = getTimeInSecondsByMinute(0);
        }
        else {
            List<? extends IRecord> records = getNewestOne(UTEPedDb.WORKOUT_TABLE, WorkoutModel.WorkoutFields.END_TIME + Table.ORDERBY_DESC);
            startTime = records.isEmpty() ? getTimeInSecondsByDay(-7) : getTimeInSecondsByDay(-2);
            endTime = getTimeInSecondsByDay(1);
        }

        Response<List<WorkoutData>> workoutResp = connection.syncWorkoutHistoryData(startTime, endTime);
        if (workoutResp.getErrorCode() == Const.CODE_SUCCESS) {
            utePedDb.saveWorkoutData(workoutResp.getData(), startTime, endTime, address);
        } else {
            Logger.d(TAG, "workout data error code: " + workoutResp.getErrorCode());
        }
    }

    private void fetchStressData(UteBleConnection connection, String address, boolean latest) {
        int startTime;
        int endTime;
        if (latest) {
            startTime = getTimeInSecondsByMinute(-5);
            endTime = getTimeInSecondsByMinute(0);
        }
        else {
            List<? extends IRecord> records = getNewestOne(UTEPedDb.STRESS_TABLE, StressModel.StressFields.CALENDAR_TIME + Table.ORDERBY_DESC);
            startTime = records.isEmpty() ? getTimeInSecondsByDay(-7) : getTimeInSecondsByDay(-2);
            endTime = getTimeInSecondsByDay(1);
        }

        Response<List<StressData>> stressResp = connection.syncStressHistoryData(startTime, endTime);
        if (stressResp.getErrorCode() == Const.CODE_SUCCESS) {
            utePedDb.saveStressData(stressResp.getData(), startTime, endTime, address);
        } else {
            Logger.d(TAG, "stress data error code: " + stressResp.getErrorCode());
        }
    }

    private void fetchSleepData(UteBleConnection connection, String address) {
        List<? extends IRecord> records = getNewestOne(UTEPedDb.SLEEP_TABLE, SleepModel.SleepFields.END_TIME + Table.ORDERBY_DESC);
        int startTime = records.isEmpty() ? getTimeInSecondsByDay(-7) : getTimeInSecondsByDay(-2);
        int endTime = getTimeInSecondsByDay(1);
        Response<List<SleepData>> sleepHistoryData = connection.syncSleepHistoryData(startTime, endTime);
        if (sleepHistoryData.getErrorCode() == Const.CODE_SUCCESS) {
            utePedDb.saveSleepData(sleepHistoryData.getData(), startTime, endTime, address);
        } else {
            Logger.d(TAG, "sleep data error code: " + sleepHistoryData.getErrorCode());
        }
    }

    private static Date parseDate(String source, String pattern) {
        @SuppressLint("SimpleDateFormat")
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        try {
            return sdf.parse(source);
        } catch (ParseException e) {
            return new Date();
        }
    }

    public void fetchDataAsync(String action) {
        if (isSyncRunning) {
            Logger.d(TAG, "syncData...is running");
            syncDataComplete(Const.STATE_SYNC_RUNNING);
        }
        else if (!UteBleClient.getUteBleClient().isConnected()) {
            Logger.d(TAG, "syncData...device not connected");
            syncDataComplete(Const.STATE_DEVICE_DISCONNECTED);
        } else if (!isSyncRunning) {
            setSyncDataTimeout();
            new Thread(() -> fetchData(action)).start();
        }
    }

    private void syncDataComplete(String value) {
        UTEPedMethodCallHandler utePedMethodCallHandler = UTEPedMethodCallHandler.shareInstance();
        if (utePedMethodCallHandler != null) {
            utePedMethodCallHandler.publishLocal(Const.ACTION_SYNC_DATA_COMPLETE, value);
        }
    }

    private void setSyncDataTimeout() {
        cancelSyncDataTimer();
        syncDataTimer = new Timer();
        syncDataTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                if (isSyncRunning) {
                    Logger.d(TAG, "syncData timeout!");
                    isSyncRunning = false;
                }
            }
        }, 60 * 1000L);
    }

    public void cancelSyncDataTimer() {
        if (syncDataTimer != null) {
            syncDataTimer.cancel();
            syncDataTimer = null;
        }
    }
}
